//#define _DEBUG

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#include <linux/device.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
#include <linux/list.h>
#include <linux/cdev.h>
#include <linux/proc_fs.h>
#include <linux/mm.h>
#include <linux/seq_file.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#ifdef _DEBUG
#include <asm-generic/signal.h>
#include <asm-generic/siginfo.h>
#endif
#ifndef _DEBUG
#include <mach/irqs.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#define __REG(addr,offset)   (*(volatile unsigned int *)IO_ADDRESS(addr + offset))

#define REG_PINMUX3     __REG(0x01c4000c,0)

#define REG_TIME0_BASE     (0x01c21400)
#define REG_TIME1_BASE     (0x01c21800)
#define REG_TIME2_BASE     (0x01c21c00)
#define REG_TIME3_BASE     (0x01c20800)
#define REG_TIME4_BASE     (0x01c23800)

#define OFFSET_PID12       (0x00)
#define OFFSET_EMUMGT      (0x04)
#define OFFSET_TIM12       (0x10)
#define OFFSET_TIM34       (0x14)
#define OFFSET_PRD12       (0x18)
#define OFFSET_PRD34       (0x1c)
#define OFFSET_TCR         (0x20)
#define OFFSET_TGCR        (0x24)
#define OFFSET_WDTCR       (0x28)
#define OFFSET_REL12       (0x34)
#define OFFSET_REL34       (0x38)
#define OFFSET_CAP12       (0x3c)
#define OFFSET_CAP34       (0x40)
#define OFFSET_INTCTL_STAT (0x44)

#define PSC_BASE     (0x01C41000)
#define OFFSET_PTCMD (0x120)
#define OFFSET_PTSTAT (0x128)
#define OFFSET_MDSTAT_TIMER1 (0x870)
#define OFFSET_MDCTL_TIMER1 (0xa70)
//#define MY_TIMER  0x01800000
//#define MY_TIMER  0x05C80  //2ms
#define MY_TIMER  0x0b900  //2ms


/* TCR bit definitions */
#define ENAMODE12_DISABLED	(0 << 6)
#define ENAMODE12_ONESHOT	(1 << 6)
#define ENAMODE12_PERIODIC	(2 << 6)
#define ENAMODE34_PERIODIC	(2 << 22)

/* TGCR bit definitions */
#define TIM12RS_UNRESET		(1 << 0)
#define TIM34RS_UNRESET		(1 << 1)
#define TIMMODE_64BIT_WDOG      (2 << 2)

#endif

#define DEVICE_NAME "irq_hz512"

// static unsigned int hz512_major = 231;
// static unsigned int hz512_minor = 0;
// static struct cdev  hz512_dev;
// static struct class *hz512dev_class;
const int irq = 34;
static int gpio_hz512 = 89;
static char elec_level = 0;
/*--------------signal related---------------------*/
struct fasync_struct *fasync_queue;
static int my_fasync(int fd, struct file * filp, int on) 
{
  int retval;
  retval=fasync_helper(fd,filp,on,&fasync_queue);
  return retval;
}
/*--------------signal related end---------------*/

static int hz512_open(struct inode *inode, struct file *filp)
{
  int status = 0;
  printk(KERN_ALERT "begin the 512 hz interrupt.\n");
  return status;
}
static int hz512_release(struct inode *inode, struct file *file)
{
  my_fasync(-1, file, 0);
  printk(KERN_ALERT "close the 512 hz interrupt.\n");
  return 0;
}
static ssize_t hz512_read(struct file *filp, char __user *buf, size_t count, loff_t *offp)
{
  unsigned long		missing;
  missing = copy_to_user(buf, &elec_level, 1);
  if (missing !=0 )
    return -EFAULT;
  return 0;
}

static ssize_t hz512_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
  ssize_t		status = 0;
  printk(KERN_ALERT "begin hz512 write.\n");
  //  struct hz512dev_data	*hz512dev;
  if (fasync_queue)
    kill_fasync(&fasync_queue, SIGIO, POLL_IN);
  return status;
}

static long hz512_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
  long retval = 0;
  switch(cmd)
  {
  default:
    gpio_set_value(gpio_hz512, 0);
    __REG(REG_TIME1_BASE,OFFSET_TCR) = ENAMODE12_PERIODIC | ENAMODE34_PERIODIC;//turn on timer again
    break;
  }
  return retval;
}
static irqreturn_t irq_handler (int irq, void *dev_id)
{
  if(elec_level > 0)
    elec_level = 0;
  else
    elec_level = 1;
  if(elec_level > 0)
    gpio_set_value(gpio_hz512, elec_level);
  else
  __REG(REG_TIME1_BASE,OFFSET_TCR) = 0;//turn off the timer, when use ioctl the timer turn on again
 
  if (fasync_queue)
    kill_fasync(&fasync_queue, SIGIO, POLL_IN);
//  udelay(20);
//    gpio_set_value(gpio_hz512, 0);
//    __REG(REG_TIME1_BASE,OFFSET_TCR) = ENAMODE12_PERIODIC | ENAMODE34_PERIODIC;//turn on timer
 // printk(KERN_ALERT "Timer1 continues\n");

  return IRQ_HANDLED;
}

static void hz512_timerInit(void)
{

#ifndef _DEBUG
  u32 timer_margin = MY_TIMER; //1s
  u32 res = __REG(PSC_BASE,OFFSET_MDSTAT_TIMER1);
    printk(KERN_ALERT "Timer1 on/off status %x\n" ,res);
  if((res & 0x0003) !=3) // the timer1 is off
  {
    printk(KERN_ALERT "OFFSET_PTSTAT status %x\n" ,__REG(PSC_BASE,OFFSET_PTSTAT));
    while(((__REG(PSC_BASE,OFFSET_PTSTAT)) & (0x0001)) > 0) //wait till no transfer
      udelay(100);
    __REG(PSC_BASE,OFFSET_MDCTL_TIMER1) = 0x703;      //set timer1 enable
    __REG(PSC_BASE,OFFSET_PTCMD) = 1;            //begin the transfer
    printk(KERN_ALERT "try to turn Timer1 on\n" );
    while(((__REG(PSC_BASE,OFFSET_PTSTAT)) & (0x0001)) > 0) //wait till transfer finished
      udelay(100);
    printk(KERN_ALERT "Timer1 status is %x \n", __REG(PSC_BASE,OFFSET_MDSTAT_TIMER1) );
  }

// set Timer1 parameters
  __REG(REG_TIME1_BASE,OFFSET_TCR) = 0;

  __REG(REG_TIME1_BASE,OFFSET_TIM12) = 1;
  __REG(REG_TIME1_BASE,OFFSET_TIM34) = 0;
  __REG(REG_TIME1_BASE,OFFSET_PRD12) = timer_margin;
  __REG(REG_TIME1_BASE,OFFSET_PRD34) = 0x00;

  __REG(REG_TIME1_BASE,OFFSET_TGCR) = TIM12RS_UNRESET | TIM34RS_UNRESET;
  __REG(REG_TIME1_BASE,OFFSET_INTCTL_STAT) = 1;

  __REG(REG_TIME1_BASE,OFFSET_TCR) = ENAMODE12_PERIODIC | ENAMODE34_PERIODIC;

#endif
/*
  for(i = 0; i<20;i++)
  {
    mdelay(10);
    timer_margin = __REG(REG_TIME1_BASE,OFFSET_TIM12);
    printk(KERN_ALERT "timer1: %x !\n",timer_margin);
  }
    timer_margin = __REG(REG_TIME1_BASE,OFFSET_PRD12);
    printk(KERN_ALERT "prd121: %x !\n",timer_margin);

    printk(KERN_ALERT "time0TGC: %x !\n",__REG(REG_TIME0_BASE,OFFSET_TGCR));
    printk(KERN_ALERT "time1TGC: %x !\n",__REG(REG_TIME1_BASE,OFFSET_TGCR));
    printk(KERN_ALERT "time4TGC: %x !\n",__REG(REG_TIME4_BASE,OFFSET_TGCR));
*/

}
static int hz512_irqInit(void)
{
#ifndef _DEBUG
  int err;
  err = request_irq(irq,irq_handler,IRQF_DISABLED,DEVICE_NAME,NULL);
  if (err){
    if(err == -EBUSY)
      printk(KERN_ALERT "request_irq %d busy!\n",irq);
    else
      printk(KERN_ALERT "request_irq failed!\n");
    return err;
  }
#endif
  printk(KERN_ALERT "%s device OK!\n",DEVICE_NAME);
  return 0;
}
static int hz512_gpioInit(void)
{
  int status;
  status = gpio_request(gpio_hz512, DEVICE_NAME);
  if(status<0)
  {    
    printk(KERN_ALERT "the hz512 gpio %d is busy!\n",gpio_hz512);
    return status;
  }
  /* gpio pins mux set */
  REG_PINMUX3 &= ~(1 << 27);  //GIO23 used as gpio
  REG_PINMUX3 &= ~(1 << 26);
  /* gpio direction set */
  gpio_direction_output(gpio_hz512, 0);
  /* gpio output set 1*/
  gpio_set_value(gpio_hz512, 1);
  return 0;
}

static struct file_operations hz512_fops = {
  .owner = THIS_MODULE,
  .open  = hz512_open,
  .read  = hz512_read,
  .write = hz512_write,
  .unlocked_ioctl = hz512_ioctl,
  .release = hz512_release,
  .fasync = my_fasync,
};
static struct miscdevice hz512_dev = {
  .minor = MISC_DYNAMIC_MINOR,
  .name = DEVICE_NAME,
  .fops = &hz512_fops,
};

static int hz512_Init(void)
{

  int res;
  res = hz512_gpioInit();
  if(res < 0)
    return res;
  res = hz512_irqInit();
  if(res < 0)
    return res;
  res = misc_register(&hz512_dev);
  if(res < 0)	{
    printk(KERN_ALERT "ERROR: hz512 cannot make device node!\n");
    return res;
  }
  hz512_timerInit();
  return 0;

}
/*------------------------------------------------------------------
*	设备驱动模块卸载
*----------------------------------------------------------------*/
static void hz512_Exit(void)
{
#ifndef _DEBUG
  __REG(REG_TIME1_BASE,OFFSET_TCR) = 0;
  free_irq(irq,NULL);
  gpio_free(gpio_hz512);
#endif
  misc_deregister(&hz512_dev);
  printk(KERN_ALERT "stop the 512 hz Timer.\n");
}
MODULE_LICENSE("GPL");
module_init(hz512_Init);
module_exit(hz512_Exit);
